package hyl.base.safe;

import org.slf4j.Logger;

import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;

import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.Strings;
import org.bouncycastle.util.encoders.Hex;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithID;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.slf4j.LoggerFactory;

import hyl.core.MyCharset;
import hyl.core.MyFun;
import hyl.core.conf.MyConfig;
import hyl.core.io.MyFile;
import hyl.core.safe.MyBase64;

/**
 * 公私钥加密解密比较费时 ,所以明文字符不能长,只能短
 * 
 * @author 阿友 37798955@qq.com
 *
 */
public class SM2 {
	private static final Logger logger = LoggerFactory.getLogger(SM2.class);
	// private SM2Engine sm2引擎;
	private SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2;
	BigInteger 私钥数;
	ECPoint 公钥点;
	private ECPublicKeyParameters publicKeyParams;
	private ECPrivateKeyParameters privateKeyParams;

	public SM2() {

	}

	public SM2(SM2Engine.Mode mode) {

		this.mode = mode;

	}

	public SM2(SM2Engine.Mode mode, String 公钥, String 私钥) {

		this.mode = mode;

		init(公钥, 私钥);
	}

	public SM2(File 配置文件) {
		if (配置文件 == null) {
			init();

		} else if (!配置文件.exists()) {
			init();
			save(配置文件.getAbsolutePath());
		} else {
			Map<String, String> 公私钥对 = MyConfig.loadIni(配置文件, MyCharset._UTF8);
			String 公钥 = 公私钥对.get("公钥");
			String 私钥 = 公私钥对.get("私钥");
			String 模式 = 公私钥对.get("模式");
			this.mode = MySm2.g模式(模式);
			init(公钥, 私钥);
		}

	}

	public void save(String 配置文件路径) {
		Map<String, String> 公私钥对 = new HashMap<>();
		File f=MyFile.openFile(配置文件路径);
		MyFun.print("配置文件路径",f.getAbsolutePath());
		String 公钥 = get公钥();
		String 私钥 = get私钥();
		公私钥对.put("公钥", 公钥);
		公私钥对.put("私钥", 私钥);
		公私钥对.put("模式", MySm2.g模式(mode));
		MyConfig.writeIni(f, 公私钥对);
	}

	public void init() {
		try {
			公钥点 = null;
			私钥数 = null;
			AsymmetricCipherKeyPair kPair = MySm2.g生成公私钥对();
			privateKeyParams = (ECPrivateKeyParameters) kPair.getPrivate();
			publicKeyParams = (ECPublicKeyParameters) kPair.getPublic();
			私钥数 = privateKeyParams.getD();
			公钥点 = publicKeyParams.getQ();

		} catch (NoSuchAlgorithmException e) {
			logger.debug("SM2初始化时出现异常:" + e.getMessage());
		}
	}

	public void init(String 公钥, String 私钥) {

		if (!MyFun.isEmpty(私钥) || !MyFun.isEmpty(公钥)) {
			ECDomainParameters 曲线 = MySm2.g生成曲线();
			公钥点 = null;
			私钥数 = null;
			if (!MyFun.isEmpty(公钥)) {
				公钥点 = 曲线.getCurve().decodePoint(Hex.decode(公钥));
				// 公钥前面的02或者03表示是压缩公钥，04表示未压缩公钥, 04的时候，可以去掉前面的04
				publicKeyParams = new ECPublicKeyParameters(公钥点, 曲线);
			}
			if (!MyFun.isEmpty(私钥)) {
				私钥数 = new BigInteger(私钥, 16);
				privateKeyParams = new ECPrivateKeyParameters(私钥数, 曲线);
			}
		}
	}

	public String get公钥() {
		return Hex.toHexString(publicKeyParams.getQ().getEncoded(false));
	}

	public String get私钥() {
		return Hex.toHexString(privateKeyParams.getD().toByteArray());
	}

	public void load私钥(String 私钥) {
		if (!MyFun.isEmpty(私钥)) {
			ECDomainParameters 曲线 = MySm2.g生成曲线();
			私钥数 = new BigInteger(私钥, 16);
			privateKeyParams = new ECPrivateKeyParameters(私钥数, 曲线);
		}
	}

	public byte[] do公钥加密(byte[] 明文) {
		SM2Engine sm2引擎 = new SM2Engine(this.mode);
		sm2引擎.init(true, new ParametersWithRandom(publicKeyParams, new SecureRandom()));
		try {
			byte[] in = 明文;
			return sm2引擎.processBlock(in, 0, in.length);
		} catch (Exception e) {
			logger.debug("SM2加密时出现异常:" + e.getMessage());
		}
		return null;
	}

	public String do公钥加密16(String 明文) {
		byte[] cipherDataByte = 明文.getBytes(StandardCharsets.UTF_8);
		return Hex.toHexString(do公钥加密(cipherDataByte));
	}

	/**
	 * 公钥,明文相同的情况下,因为有盐,所以每次加密结果也是不一样的
	 * 
	 * @param 明文
	 * @return
	 */
	public String do公钥加密64(String 明文) {
		return MyBase64.encode(do公钥加密(明文.getBytes()));
	}

	public byte[] do私钥解密(byte[] 密文) {
		SM2Engine sm2引擎 = new SM2Engine(this.mode);
		sm2引擎.init(false, privateKeyParams);
		try {
			return sm2引擎.processBlock(密文, 0, 密文.length);

		} catch (Exception e) {
			logger.debug("SM2解密时出现异常:" + e.getMessage());
		}
		return null;
	}

	// 大部分时候都是客户端加密 服务端解密
	public String do私钥解密16(String 密文) {
		// 设置sm2为解密模式
		if (!密文.startsWith("04"))
			密文 = "04" + 密文;

		byte b[]=do私钥解密(Hex.decode(密文));
		return new String(b,StandardCharsets.UTF_8);
	}

	public String do私钥解密64(String 密文) {
		// 设置sm2为解密模式

		return new String(do私钥解密(MyBase64.decode(密文)));
	}

	public byte[] g私钥签名(byte[] 明文, byte[] id) {
		SM2Signer sm2 = new SM2Signer(new SM3Digest());// new SM3Digest()

		ParametersWithID cp;
		try {
			cp = new ParametersWithID(new ParametersWithRandom(privateKeyParams, SecureRandom.getInstance("SHA1PRNG")),
					id);
			sm2.init(true, cp);
		} catch (NoSuchAlgorithmException e1) {
			e1.printStackTrace();
		}

		sm2.update(明文, 0, 明文.length);
		try {
			return sm2.generateSignature();
		} catch (CryptoException e) {
			logger.debug("SM2签名时出现异常:" + e.getMessage());
			return null;
		}
	}

	public boolean do验证签名(byte[] 明文, byte[] 签名, byte[] id) {
		SM2Signer sm2 = new SM2Signer(new SM3Digest());//
		ParametersWithID pid = new ParametersWithID(publicKeyParams, id);
		sm2.init(false, pid);
		sm2.update(明文, 0, 明文.length);
		return sm2.verifySignature(签名);
	}

	public String g私钥签名64(String 明文, String id) {
		byte[] message = g私钥签名(明文.getBytes(), Strings.toByteArray(id));
		return MyBase64.encodeUri(message);

	}

	public boolean do验证签名64(String 明文, String 签名, String id) {
		return do验证签名(明文.getBytes(), MyBase64.decodeUri(签名), Strings.toByteArray(id));
	}

	/**
	 * 
	 * @param 明文
	 * @param 用户id 16个ascii字符
	 * @return
	 */
	public String g私钥签名16(String 明文, String 用户id) {
		ECDomainParameters 曲线 = MySm2.g生成曲线();
		byte[] message = 明文.getBytes();
		// 创建签名实例
		byte[] signBytes = g私钥签名(message, Strings.toByteArray(用户id));
		int curveLen = MySm2.getCurveLength(曲线);
		// start bc1.57版本中，signData是纯r+s字符串拼接，如果为了兼容低版本的bc包，则需要加这一句
		byte[] signData = MySm2.do签名DER解码(curveLen, signBytes);
		// end
		String sign = Hex.toHexString(signData);
		return sign;
	}

	/**
	 * 
	 * @param 明文
	 * @param 签名
	 * @param 用户id 格式:16个ascii字符
	 * @return
	 */
	public boolean do验证签名16(String 明文, String 签名, String 用户id) {
		// 待签名内容
		byte[] message = Hex.decode(Hex.toHexString(明文.getBytes()));
		byte[] signData = Hex.decode(签名);
		try {
			signData = MySm2.do签名DER编码(signData);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
		// 验证签名结果
		return do验证签名(message, signData, Strings.toByteArray(用户id));

	}

	public static void main(String[] args) {
		SM2 sm = new SM2();
		// sm.init();
		sm.init("0420b6aa10e86195819252ceee9504970ea14f06e5b346f340803256867199583b90ebb73e94e6951bcbe9ab537db61eabeb1c9a2ae922f0d8f1fd03ad4106b70b",
				"35b694fbc559337ed1388180973d22e809f1aa67d59dee166f20a4168855dba7");
		String 明文="74C37A2ABAECBBBC995D65E73894DF37";
		String gy = sm.get公钥();
		MyFun.print("公钥",gy);
		String sy = sm.get私钥();
		MyFun.print("私钥",sy);

		String m = sm.do公钥加密16(明文);
		MyFun.print(m);
		String m2 = sm.do私钥解密16(m);
		MyFun.print(m2);
		
		
		m="f6845f2345e3509a5cd42f1577c7644e3233aed72b3679708a881787ff66a2eddd36eebad598a02a55e258cd80029002893af689c92e98c87001d400ac5b6daace915aef46da2b1c360fd1b7c2126acdbfd865b55cc05db0f422e854b8baeb8c5d1605096a6a0c503aeaeccc33a9a4d6d5d59bc7595fe73076e9ee279df44b8d";
		m2 = sm.do私钥解密16(m);
		MyFun.print("2",m2);
		
		
		m = sm.do公钥加密64(明文);
		MyFun.print(m);
		m2 = sm.do私钥解密64(m);
		MyFun.print(m2);
		m2 = sm.do私钥解密64(
				"BEJ0TuCD8ayZPKhwlvAvaEYhKbP5VaXnvcY1J8jQuiV8RYQF84NxwqHdYHwN9mnyqzunCVPOdZR1+dzyMYVnUX3Jengs0HZrlnKq8xZ+vYku6HmmNlYZNZ3yUHF4nWVzwYBIvlDQe9P6/Gvj9raIvuTFbcxi2nmIfVs7p737OLz5");
		MyFun.print(m2);

		m = sm.g私钥签名16("金砂东路华工科技为了请问另外其他乾隆通宝布34下次军训洛芬", "12345678");
		MyFun.print(m);
		boolean ff = sm.do验证签名16("金砂东路华工科技为了请问另外其他乾隆通宝布34下次军训洛芬", m, "12345678");
		MyFun.print(ff);

		m = sm.g私钥签名64("金砂东路华工科技为了请问另外其他乾隆通宝布34下次军训洛芬", "12345678");
		MyFun.print(m);
		ff = sm.do验证签名64("金砂东路华工科技为了请问另外其他乾隆通宝布34下次军训洛芬", m, "12345678");
		MyFun.print(ff);
	}

}
